﻿using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;

namespace Devonline.AspNetCore;

/// <summary>
/// 数据带附件及附加信息的基础数据服务
/// 业务数据的附件全都在名为 Attachments 的集合中, 根据 BusinessType 字段区分用途
/// </summary>
/// <typeparam name="TDbContext">数据库上下文</typeparam>
/// <typeparam name="TEntitySet">业务数据类型</typeparam>
/// <typeparam name="TElement">集合元素数据类型</typeparam>
/// <typeparam name="TAttachment">附件数据类型</typeparam>
/// <typeparam name="TKey">主键类型</typeparam>
public class DataWithCollectionAndAttachmentService<TDbContext, TEntitySet, TElement, TAttachment, TKey>(
    ILogger<DataWithCollectionAndAttachmentService<TDbContext, TEntitySet, TElement, TAttachment, TKey>> logger,
    TDbContext context,
    IDistributedCache cache,
    IHttpContextAccessor httpContextAccessor,
    IFileService fileService,
    HttpSetting httpSetting,
    IDataWithCollectionService<TDbContext, TEntitySet, TElement, TKey> dataWithCollectionService) :
    DataWithAttachmentService<TDbContext, TEntitySet, TAttachment, TKey>(logger, context, cache, httpContextAccessor, fileService, httpSetting),
    IDataWithCollectionAndAttachmentService<TDbContext, TEntitySet, TElement, TAttachment, TKey>,
    IDataWithCollectionService<TDbContext, TEntitySet, TElement, TKey>,
    IDataWithAttachmentService<TDbContext, TEntitySet, TAttachment, TKey>,
    IDataService<TDbContext, TEntitySet, TKey>,
    IDataCollectionService<TDbContext, TEntitySet, TElement, TKey>,
    IDataAttachmentService<TDbContext, TEntitySet, TAttachment, TKey>
    where TDbContext : DbContext
    where TEntitySet : class, IEntitySet<TKey>, new()
    where TElement : class, IEntitySet<TKey>
    where TAttachment : class, IAttachment<TKey>
    where TKey : IConvertible
{
    protected readonly Type _elementType = typeof(TElement);
    protected readonly IDataWithCollectionService<TDbContext, TEntitySet, TElement, TKey> _dataWithCollectionService = dataWithCollectionService;

    #region 实现自 IDataWithCollectionService 接口的方法
    /// <summary>
    /// 新增业务数据对象中 TElement 类型的集合对象
    /// </summary>
    /// <param name="entitySet">业务数据</param>
    /// <param name="elements">引用数据集合</param>
    /// <param name="foreignKey">外键字段名</param>
    /// <returns></returns>
    public virtual Task AddCollectionAsync(TEntitySet entitySet, IEnumerable<TElement>? elements = default, string? foreignKey = default) => _dataWithCollectionService.AddCollectionAsync(entitySet, elements, foreignKey);
    /// <summary>
    /// 更新业务数据对象中 TElement 类型的集合对象
    /// </summary>
    /// <param name="entitySet">业务数据</param>
    /// <param name="elements">引用数据集合</param>
    /// <param name="foreignKey">外键字段名</param>
    /// <param name="isLogical">是否逻辑操作, 默认不是</param>
    /// <returns></returns>
    public virtual Task UpdateCollectionAsync(TEntitySet entitySet, IEnumerable<TElement>? elements = default, string? foreignKey = default, bool isLogical = false) => _dataWithCollectionService.UpdateCollectionAsync(entitySet, elements, foreignKey, isLogical);
    /// <summary>
    /// 删除业务数据对象中 TElement 类型的集合对象
    /// </summary>
    /// <param name="id">业务数据主键</param>
    /// <param name="foreignKey">外键字段名</param>
    /// <param name="isLogical">是否逻辑操作, 默认不是</param>
    /// <returns></returns>
    public virtual Task DeleteCollectionAsync(TKey id, string? foreignKey = default, bool isLogical = false) => _dataWithCollectionService.DeleteCollectionAsync(id, foreignKey, isLogical);
    #endregion

    #region 继承自 DataService 的方法
    /// <summary>
    /// 此重载新增方法会同时将当前业务数据的附加信息及附件一并新增
    /// </summary>
    /// <param name="entitySet">业务数据</param>
    /// <param name="context">数据操作上下文</param>
    protected override async Task InternalAddAsync(TEntitySet entitySet, DataServiceContext? context = default)
    {
        _logger.LogInformation($"The user {UserName} will add {_typeName} and it's related collections, attachments, the content is: {entitySet.ToJsonString()}");

        await base.InternalAddAsync(entitySet, context);
        var elements = entitySet.GetCollectionMembers<TEntitySet, TElement>();
        var foreignKeys = _elementType.GetForeignKeys<TEntitySet, TKey>();
        foreach (var element in elements)
        {
            var foreignKey = (context?.ForeignKeys?.ContainsKey(element.Key) ?? false) ? context.ForeignKeys[element.Key] : foreignKeys.FirstOrDefault(x => x.Key == element.Key).Value;
            await AddCollectionAsync(entitySet, element.Value, foreignKey);
        }
    }
    /// <summary>
    /// 重载的更新方法会同时将当前业务数据的附加信息及附件一并更新
    /// </summary>
    /// <param name="entitySet">业务数据</param>
    /// <param name="context">数据操作上下文</param>
    /// <returns></returns>
    protected override async Task InternalUpdateAsync(TEntitySet entitySet, DataServiceContext? context = default)
    {
        var isLogical = context?.IsLogical ?? false;
        _logger.LogInformation($"The user {UserName} will {GetLogicalString(isLogical)} update {_typeName} and it's related collections, attachments, the content is: {entitySet.ToJsonString()}");

        await base.InternalUpdateAsync(entitySet, context);

        //TODO TBC 在主数据对象中出现多个同类型集合时, 此处可能会有问题
        var elements = entitySet.GetCollectionMembers<TEntitySet, TElement>();
        var foreignKeys = _elementType.GetForeignKeys<TEntitySet, TKey>();
        foreach (var element in elements)
        {
            var foreignKey = (context?.ForeignKeys?.ContainsKey(element.Key) ?? false) ? context.ForeignKeys[element.Key] : foreignKeys.FirstOrDefault(x => x.Key == element.Key).Value;
            await UpdateCollectionAsync(entitySet, element.Value, foreignKey, isLogical);
        }
    }
    /// <summary>
    /// 重载的删除方法会同时将当前业务数据的附加信息及附件一并删除
    /// </summary>
    /// <param name="id">业务数据主键</param>
    /// <param name="context">数据操作上下文</param>
    /// <returns></returns>
    protected override async Task InternalDeleteAsync(TKey id, DataServiceContext? context = default)
    {
        var isLogical = context?.IsLogical ?? false;
        _logger.LogInformation($"The user {UserName} will {GetLogicalString(isLogical)} delete {_typeName} and it's related collections, attachments, the content id is: {id}");

        await base.InternalDeleteAsync(id, context);
        var foreignKeys = context?.ForeignKeys?.Values ?? _elementType.GetForeignKeys<TEntitySet, TKey>().Values;
        foreach (var foreignKey in foreignKeys)
        {
            await DeleteCollectionAsync(id, foreignKey, isLogical);
        }
    }
    #endregion
}

/// <summary>
/// 数据带附件及附加信息的基础数据服务
/// 字符串作为主键的默认实现
/// 业务数据的附件全都在名为 Attachments 的集合中, 根据 BusinessType 字段区分用途
/// </summary>
/// <typeparam name="TDbContext">数据库上下文</typeparam>
/// <typeparam name="TEntitySet">业务数据类型</typeparam>
/// <typeparam name="TElement">集合元素数据类型</typeparam>
public class DataWithCollectionAndAttachmentService<TDbContext, TEntitySet, TElement>(
    ILogger<DataWithCollectionAndAttachmentService<TDbContext, TEntitySet, TElement>> logger,
    TDbContext context,
    IDistributedCache cache,
    IHttpContextAccessor httpContextAccessor,
    IFileService fileService,
    HttpSetting httpSetting,
    IDataWithCollectionService<TDbContext, TEntitySet, TElement> dataWithCollectionService) :
    DataWithCollectionAndAttachmentService<TDbContext, TEntitySet, TElement, Attachment, string>(logger, context, cache, httpContextAccessor, fileService, httpSetting, dataWithCollectionService),
    IDataWithCollectionAndAttachmentService<TDbContext, TEntitySet, TElement>,
    IDataWithCollectionService<TDbContext, TEntitySet, TElement>,
    IDataWithAttachmentService<TDbContext, TEntitySet>,
    IDataService<TDbContext, TEntitySet>,
    IDataCollectionService<TDbContext, TEntitySet, TElement>,
    IDataAttachmentService<TDbContext, TEntitySet>
    where TDbContext : DbContext
    where TEntitySet : class, IEntitySet, new()
    where TElement : class, IEntitySet;